
'use strict';

var canvas = document.querySelector('canvas');
var switchCaptureButton = document.getElementById('switchCapture');
var offerButton = document.getElementById('offerButton');
var pranswerButton = document.getElementById('pranswerButton');
var answerButton = document.getElementById('answerButton');
var hangupButton = document.getElementById('hangupButton');
var pc1StateDiv = document.querySelector('div#pcState1');
var pc1IceStateDiv = document.querySelector('div#pcIceState1');
var pc2StateDiv = document.querySelector('div#pcState2');
var pc2IceStateDiv = document.querySelector('div#pcIceState2');

var localSDPSel = 'textarea#localSDP';
var localCandsSel = 'textarea#localCandidates';
var remoteSDPSel = 'textarea#answerSDP';
var remoteCandsSel = 'textarea#answerCandidates';

var localSDPTextarea = document.querySelector(localSDPSel);
var localCandidatesTextarea = document.querySelector(localCandsSel);
var remoteSDPTextarea = document.querySelector(remoteSDPSel);
var remoteCandidatesTextarea = document.querySelector(remoteCandsSel);
var localCandidates = new Array();
var remoteCandidates = new Array();

// var pcConfig = {
//   "iceServers": [{
//       "url": "stun:121.41.87.159:3478"
//   }]
//   , "rtcpMuxPolicy" : "require"
//   , "bundlePolicy" :  "max-bundle"
// };

var pcConfig = {};
pcConfig["iceServers"]=[];
pcConfig["rtcpMuxPolicy"] = "require";
pcConfig["bundlePolicy"] = "max-bundle";

var offerOptions = {
  offerToReceiveAudio: 1,
  offerToReceiveVideo: 1
};

var sdpConstraints = {
  'mandatory': {
    'OfferToReceiveAudio': true,
    'OfferToReceiveVideo': true
  }
};

var startTime;
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
var localVideo2 = document.getElementById('localVideo2');
var remoteVideo2 = document.getElementById('remoteVideo2');
var localStream = null;
var localStream2 = null;
var pc1;
var pc2;




switchCaptureButtonText(true);
switchCaptureButton.onclick = onClickSwitchCapture;
hangupButton.onclick = onClickHangup;




localVideo.addEventListener('loadedmetadata', function() {
  console.log('Local video videoWidth: ' + this.videoWidth +
    'px,  videoHeight: ' + this.videoHeight + 'px');
});

remoteVideo.addEventListener('loadedmetadata', function() {
  console.log('Remote video videoWidth: ' + this.videoWidth +
    'px,  videoHeight: ' + this.videoHeight + 'px');
});

remoteVideo.onresize = function() {
  console.log('Remote video size changed to ' +
    remoteVideo.videoWidth + 'x' + remoteVideo.videoHeight);
  // We'll use the first onsize callback as an indication that video has started
  // playing out.
  if (startTime) {
    var elapsedTime = window.performance.now() - startTime;
    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');
    startTime = null;
  }
};

remoteVideo.onplaying = function(){
  console.log("remoteVideo.onplaying");
};

function switchCaptureButtonText(on){
  switchCaptureButton.innerHTML =  'Camera';
  // if(on){
  //   switchCaptureButton.innerHTML =  'camera off';
  // }else{
  //   switchCaptureButton.innerHTML =  'camera on';
  // }
}

var alreadyAnswer = false;
updateButtonState();
function updateButtonState(){
  var pc1exist = pc1 ? true : false;
  var pc2exist = pc2 ? true : false;
  console.log('pc1exist ' + pc1exist);
  console.log('pc2exist ' + pc2exist);

  if(!pc1exist){
    alreadyAnswer = false;
  }
  
  offerButton.disabled = pc1exist;
  hangupButton.disabled = !pc1exist;
  switchCaptureButton.disabled = !pc1exist;
  pranswerButton.disabled = !pc1exist || (pc1exist && pc2exist);
  answerButton.disabled = !pc1exist || (pc1exist && pc2exist && alreadyAnswer);
}


function onClickSwitchCapture() {
  if(localStream){
    var enabled = true;
    localStream.getTracks().forEach(function (track) {
        if(track.kind == 'video'){
          track.enabled = !track.enabled;
          switchCaptureButtonText(track.enabled);
          console.log("video enable " + track.enabled);
          return ;
        }
    });
  }
}


function onClickHangup() {
  console.log('Ending call');

  if(pc1){
    closePeerconnnection(pc1, pc1StateDiv, pc1IceStateDiv, localStream);
    pc1 = null;
    localStream = null;
  }

  if(pc2){
    closePeerconnnection(pc2, pc2StateDiv, pc2IceStateDiv, localStream2);
    pc2 = null;
    localStream2 = null;
  }

  updateButtonState();

}

function closePeerconnnection(pc, stateDiv, iceStateDiv, localStreamT){
  if(pc){
    // stateDiv.textContent += ' => ' + pc.signalingState || pc.readyState;
    // iceStateDiv.textContent += ' => ' + pc.iceConnectionState;

    pc.close();
    pc = null;
  }

  if(localStreamT){
    localStreamT.getTracks().forEach(function (track) {
                    track.stop();
                });
    localStreamT = null;
  }
}

function createPeerconnnection(name, stateDiv, iceStateDiv, localVideoT, remoteVideoT, onCandidateCallback){
  var pc;
  pc = new RTCPeerConnection(pcConfig);
  console.log('Created local peer connection object ' + name);
  
  stateDiv.textContent = pc.signalingState || pc.readyState;
  iceStateDiv.textContent = pc.iceConnectionState;

  pc.onicecandidate = function(event) {
    // console.log(name + ' got onicecandidate: ', event);
    onCandidateCallback(pc, event);
    if(event.target.iceGatheringState === 'complete'){
      console.log(name + ' iceGatheringState -> done ' + event.target.iceGatheringState);
    }
  };

  pc.onicegatheringstatechange = function(){
    // never call here
    console.log(name + ' iceGatheringState -> ' + event.target.iceGatheringState);
  }

  pc.oniceconnectionstatechange = function(event) {
    console.log(name + ' ICE state: ' + pc.iceConnectionState);
    // console.log('ICE state change event---: ', event);
    iceStateDiv.textContent += ' => ' + pc.iceConnectionState;
  };


  pc.onsignalingstatechange = function(){
    var state;
    state = pc.signalingState || pc.readyState;
    console.log(name + ' state change => ' + state);
    stateDiv.textContent += ' => ' + state;
  };

  pc.onaddstream = function (e){
    console.log(name + ' received remote stream', e);
    // Call the polyfill wrapper to attach the media stream to this element.
    console.log(name + ' remote stream tracks ', e.stream.getTracks());
    console.log(name + ' attach remote stream');
    attachMediaStream(remoteVideoT, e.stream);
    // remoteVideoT.srcObject = e.stream;
    // if(name == 'pc1'){
    //   var streamVisualizer = new StreamVisualizer(e.stream, canvas);
    //   streamVisualizer.start();
    // }
    
  };
  return pc;
}

function maybeCreatePeerconnection(name){
  if(name == 'pc1' && !pc1) {
    pc1 = createPeerconnnection(name, pc1StateDiv, pc1IceStateDiv, localVideo, remoteVideo, onIceCandidate1);
  }

  if(name == 'pc2' &&!pc2) {
    pc2 = createPeerconnnection(name, pc2StateDiv, pc2IceStateDiv, localVideo2, remoteVideo2, onIceCandidate2);
  }
}



function onIceCandidate1(pc, event) {
  if (event.candidate) {
    localCandidates.push(event.candidate);
    localCandidatesTextarea.value = JSON.stringify( localCandidates );
    // console.log('pc1 local ICE candidate: \n' + event.candidate.candidate);
    // pc2.addIceCandidate(new RTCIceCandidate(event.candidate)); 
  }
}

function onIceCandidate2(pc, event) {
  if (event.candidate) {
    remoteCandidates.push(event.candidate);
    remoteCandidatesTextarea.value = JSON.stringify( remoteCandidates );
    console.log('pc2 local ICE candidate: \n' + event.candidate.candidate);
    // console.log('pc2 candidate -> pc1 \n');
    pc1.addIceCandidate(new RTCIceCandidate(event.candidate)); 
    // if(event.target.iceGatheringState === 'complete'){
    //   setPc2CandidateToPc1();
    // }

  }
}



var util_extend = function () {
    var self = this;
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // Handle a deep copy situation
    if (typeof target === "boolean") {
        deep = target;

        // Skip the boolean and the target
        target = arguments[i] || {};
        i++;
    }

    // Handle case when target is a string or something (possible in deep
    // copy)
    if (typeof target !== "object" && !self.isFunction(target)) {
        target = {};
    }

    // Extend self itself if only one argument is passed
    if (i === length) {
        target = this;
        i--;
    }

    for (; i < length; i++) {

        // Only deal with non-null/undefined values
        if (( options = arguments[i] ) != null) {

            // Extend the base object
            for (name in options) {
                src = target[name];
                copy = options[name];

                // Prevent never-ending loop
                if (target === copy) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                if (deep && copy && ( self.isPlainObject(copy) ||
                    ( copyIsArray = self.isArray(copy) ) )) {

                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && self.isArray(src) ? src : [];

                    } else {
                        clone = src && self.isPlainObject(src) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[name] = self.extend(deep, clone, copy);

                    // Don't bring in undefined values
                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
}





var _WebrtcStatistics = {
  bytesPrev: null,
  timestampPrev: null,
  sentBytesPrev: null,
  sentTimestampPrev: null,

  parseRecvStatistics: function (results, callback, callbackSent) {
    var self = this;

   // calculate video bitrate
    var bitrate;
    var remoteWidth;
    var remoteHeight;

    var activeCandidatePair = null;
    var remoteCandidate = null;

    Object.keys(results).forEach(function(result) {
      var report = results[result];
      var now = report.timestamp;

      
      if (report.type === 'inboundrtp' && report.mediaType === 'video') {
        // firefox calculates the bitrate for us
        // https://bugzilla.mozilla.org/show_bug.cgi?id=951496
        bitrate = Math.floor(report.bitrateMean / 1024);
      } else if (report.type === 'ssrc' && report.bytesReceived &&
           report.googFrameHeightReceived) {
        remoteWidth = report.googFrameWidthReceived;
        remoteHeight = report.googFrameHeightReceived;
        // chrome does not so we need to do it ourselves
        var bytes = report.bytesReceived;
        if (self.timestampPrev) {
          bitrate = 8 * (bytes - self.bytesPrev) / (now - self.timestampPrev);
          bitrate = Math.floor(bitrate);
        }
        self.bytesPrev = bytes;
        self.timestampPrev = now;
      }

      if (report.type === 'candidatepair' && report.selected ||
          report.type === 'googCandidatePair' &&
          report.googActiveConnection === 'true') {
        activeCandidatePair = report;
      }

      if(report.type === 'outboundrtp' && report.mediaType === 'video'){
        callbackSent('Video Bitrate', Math.floor(report.bitrateMean / 1024) + ' kbps');
      }else if (report.type === 'ssrc' && report.bytesSent &&
           report.googFrameHeightSent) {
        // chrome does not so we need to do it ourselves
        var bytes = report.bytesSent;
        if (self.sentTimestampPrev) {
          var br = 8 * (bytes - self.sentBytesPrev) / (now - self.sentTimestampPrev);
          br = Math.floor(br);
          callbackSent('Video Bitrate', br+' kbps');
          callbackSent('Video Size', report.googFrameWidthSent + 'x' + report.googFrameHeightSent);
        }
        self.sentBytesPrev = bytes;
        self.sentTimestampPrev = now;
      }


    });
  
    if (activeCandidatePair && activeCandidatePair.remoteCandidateId) {
      remoteCandidate = results[activeCandidatePair.remoteCandidateId];
    }
    if (remoteCandidate && remoteCandidate.ipAddress &&
        remoteCandidate.portNumber) {
      callback('Peer', remoteCandidate.ipAddress + ':' + remoteCandidate.portNumber);
    }

    if(bitrate){
      callback('Video Bitrate', bitrate + ' kbps');
    }

    if(remoteHeight){
      callback('Video Size', remoteWidth + 'x' + remoteHeight);
    }

  }
}

function displayStatistics() {
  if(!pc1){
    // console.log('Not start yet');
    return ;
  }
  pc1.getStats(null, function(results) {
    
    recvInfoDiv.innerHTML =  'Time: ' + new Date() + '<br/>';
    sentInfoDiv.innerHTML =  'Time: ' + new Date() + '<br/>';

    statistics.parseRecvStatistics(results, function(name, value){
      recvInfoDiv.innerHTML += name + ': ' + value + '<br/>';
    }
    , function(name, value){
      sentInfoDiv.innerHTML += name + ': ' + value + '<br/>';
    });
  });
  
}

var WebrtcStatisticsSection = function () {
    util_extend(this, _WebrtcStatistics);
};

// Display statistics
var recvInfoDiv = document.querySelector('div#recvInfo');
var sentInfoDiv = document.querySelector('div#sentInfo');
var statistics = new WebrtcStatisticsSection();

setInterval(displayStatistics, 1000);


// =============== caller part ================

if(offerButton){
  offerButton.onclick = onClickOffer;  
}

function onClickOffer() {
  console.log('click Offer button');
  console.log('Requesting local stream');
  offerButton.disabled = true;
  navigator.mediaDevices.getUserMedia({
    audio: true,
    video: true
  }).then(function (stream) {
    console.log('got local stream');
    attachMediaStream(localVideo, stream);
    localStream = stream;
    var streamVisualizer = new StreamVisualizer(stream, canvas);
    streamVisualizer.start();

    createMyOffer();
  })
  .catch(function(e) {
    console.log('error:' , e);
    alert('getUserMedia() error: ' + e.name);
    offerButton.disabled = false;
  });

}

function createMyOffer(){

  startTime = window.performance.now();
  var videoTracks = localStream.getVideoTracks();
  var audioTracks = localStream.getAudioTracks();
  if (videoTracks.length > 0) {
    console.log('Using video device: ' + videoTracks[0].label);
  }
  if (audioTracks.length > 0) {
    console.log('Using audio device: ' + audioTracks[0].label);
  }

  maybeCreatePeerconnection('pc1');

  console.log('Added local stream to pc1');
  pc1.addStream(localStream);
  
  console.log('pc1 createOffer start');
  //pc1.createOffer(offerOptions).then(
  pc1.createOffer().then(
    function (desc){
      console.log('Offer from pc1\n' + desc.sdp);
      localSDPTextarea.value = JSON.stringify( desc );
      pc1.setLocalDescription(desc
        , function() {
          console.log('pc1 setLocalDescription success');
        }
        , function(error){
          console.log('pc1 Failed to set local description: ' + error.toString());
        });

    }
    , function (error){
      console.log('Failed to create session description: ' + error.toString());
    }
  );
  updateButtonState();
}


// =============== callee part ================

if(pranswerButton){
  pranswerButton.onclick = function() {
    maybeCreatePeerconnection('pc2');
    maybeSetSdpToPc2(createMyPranswer);
    updateButtonState();
  };  
}


if(answerButton){
  answerButton.onclick = function() {
    maybeCreatePeerconnection('pc2');
    maybeOpenLocalStream(function(){
      maybeSetSdpToPc2(createMyAnswer);
    });
    alreadyAnswer = true;
    updateButtonState();
  };  
}

function maybeSetSdpToPc2(nextfunc){
    if(pc2.remoteDescription && pc2.remoteDescription.sdp != ''){
      nextfunc();
    }else{
      var descraw = JSON.parse(localSDPTextarea.value);
      var desc = new RTCSessionDescription(descraw);

      console.log('set offer to pc2...\n');
      pc2.setRemoteDescription(desc
        , function() {
          console.log('set offer to pc2 success');
          setPc1CandidateToPc2();
          nextfunc();
        }
        , function(error){
          console.log('set offer to pc2 error: ' + error.toString());
        });
    }
}

function maybeOpenLocalStream(nextfunc){
  if(!localStream2){
    navigator.mediaDevices.getUserMedia({
      audio: true,
      video: true
    }).then(function (stream) {
      console.log('got local stream2');
      attachMediaStream(localVideo2, stream);
      localStream2 = stream;
      nextfunc();
    })
    .catch(function(e) {
      alert('getUserMedia() error: ' + e.name);
      offerButton.disabled = false;
    });
  }else{
    nextfunc();
  }
}



function createMyPranswer(){

  console.log('pc2 create pranswer ...');
  pc2.createAnswer(
    function (desc){
      console.log('pc2 create pranswer success');
      desc.type = "pranswer";
      desc.sdp = desc.sdp.replace(/a=recvonly/g, 'a=inactive');
      console.log('pranswer from pc2\n' + desc.sdp);
      remoteSDPTextarea.value = JSON.stringify( desc );

      pc2.setLocalDescription(desc
        , function() {
          console.log('pc2 setLocalDescription success');
          setPc2SdpToPc1('pranswer');
        }
        , function(error){
          console.log('pc2 Failed to set local description: ' + error.toString());
        });

    }
    , function (error){
      console.log('pc2 Failed to create session description: ' + error.toString());
    }
    , sdpConstraints
  );

}

function setPc1CandidateToPc2(){
  console.log('pc1 candidates -> pc2');
  var candidates = JSON.parse(localCandidatesTextarea.value);
  candidates.forEach(function(e){  
    pc2.addIceCandidate(new RTCIceCandidate(e)); 
  });
}

function setPc2CandidateToPc1(){
  console.log('pc2 candidates -> pc1');
  var candidates = JSON.parse(remoteCandidatesTextarea.value);
  candidates.forEach(function(e){  
    pc1.addIceCandidate(new RTCIceCandidate(e)); 
  });
}


function setPc2SdpToPc1(sdptype){
  var descraw = JSON.parse(remoteSDPTextarea.value);
  var desc = new RTCSessionDescription(descraw);

  console.log('pc1 set remote sdp (' + sdptype + ') ...');
  pc1.setRemoteDescription(desc
    , function() {
      console.log('pc1 set remote sdp (' + sdptype + ') success');
    }
    , function(error){
      console.log('pc1 set remote sdp (' + sdptype + ') error: ' + error.toString());
    });
}


function createMyAnswer(){
  if(!localStream2){
    console.log('can NOT create answer beacause no local stream\n');
    return;
  }

  startTime = window.performance.now();
  var videoTracks = localStream2.getVideoTracks();
  var audioTracks = localStream2.getAudioTracks();
  if (videoTracks.length > 0) {
    console.log('pc2 Using video device: ' + videoTracks[0].label);
  }
  if (audioTracks.length > 0) {
    console.log('pc2 Using audio device: ' + audioTracks[0].label);
  }

  console.log('Added local stream2 to pc2');
  pc2.addStream(localStream2);




  pc2.createAnswer(
    function (desc){
      console.log('answer from pc2\n' + desc.sdp);
      remoteSDPTextarea.value = JSON.stringify( desc );

      pc2.setLocalDescription(desc
        , function() {
          console.log('pc2 setLocalDescription success');
          setPc2SdpToPc1('answer');
          // var delay = 15000;
          // console.log('exec setPc2SdpToPc1 answer after ' + delay + ' ms');
          // setTimeout("setPc2SdpToPc1('answer')", delay);
          
        }
        , function(error){
          console.log('pc2 Failed to set local description: ' + error.toString());
        });
    }
    , function (error){
      console.log('pc2 Failed to create session description: ' + error.toString());
    }
    , sdpConstraints
  );
}


